package com.enation.app.javashop.framework.util;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Redis操作工具类
 *
 * @author jian.sun
 */

@Service
public final class RedisUtils {

    private RedisUtils() {
    }

    public static final String DEFAULT_KEY = "DEFAULT_KEY";

    private static StringRedisTemplate stringRedisTemplate;

    @Autowired
    public void setRedisTemplate(StringRedisTemplate redisTemplate) {
        RedisUtils.stringRedisTemplate = redisTemplate;
    }

    // 序列化对象
    // JSON.parseObject()有个bug 传x和\都会报错
    private static ObjectMapper objectMapper = new ObjectMapper();

    static {
        objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }


    /******************************添加*****************************/

    /**
     * 添加键值 - 使用普通键值对存储
     *
     * @param key   键
     * @param value 值
     */
    public static void set(String key, Object value) {
        value = value == null ? "" : value;
        if (value instanceof String) {
            stringRedisTemplate.opsForValue().set(key, value.toString());
        } else {
            try {
                String strValue = objectMapper.writeValueAsString(value);
                stringRedisTemplate.opsForValue().set(key, strValue);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 添加键值 - 使用普通键值对存储
     * 如果键不存在则新增，存在则不改变已经有的值
     *
     * @param key   键
     * @param value 值
     */
    public static boolean setIfAbsent(String key, Object value) {
        value = value == null ? "" : value;
        if (value instanceof String) {
            return stringRedisTemplate.opsForValue().setIfAbsent(key, value.toString());
        } else {
            try {
                String strValue = objectMapper.writeValueAsString(value);
                return stringRedisTemplate.opsForValue().setIfAbsent(key, strValue);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return false;
    }


    /**
     * 添加键值  - 使用普通键值对存储
     *
     * @param key    键
     * @param value  值
     * @param expire 过期时间(秒)
     */
    public static void set(String key, Object value, long expire) {
        value = value == null ? "" : value;
        set(key, value);

        // 设置过期时间
        expire(key, expire);
    }


    /**
     * 添加键值 - 原子操作 - 使用普通键值对存储
     *
     * @param key   键
     * @param value 值
     */
    public static String getAndSet(String key, Object value) {
        value = value == null ? "" : value;
        if (value instanceof String) {
            return stringRedisTemplate.opsForValue().getAndSet(key, value.toString());
        } else {
            try {
                String strValue = objectMapper.writeValueAsString(value);
                return stringRedisTemplate.opsForValue().getAndSet(key, strValue);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return null;
    }


    /**
     * 添加键值到默认哈希表中
     *
     * @param field 字段名
     * @param value 值
     */
    public static void hSet(String field, Object value) {
        hSet(DEFAULT_KEY, field, value);
    }


    /**
     * 添加键值到指定的哈希表中
     *
     * @param key   键
     * @param field 字段名
     * @param value 值
     */
    public static void hSet(String key, String field, Object value) {
        value = value == null ? "" : value;
        if (value instanceof String) {
            stringRedisTemplate.opsForHash().put(key, field, value.toString());
        } else {
            try {
                String strValue = objectMapper.writeValueAsString(value);
                stringRedisTemplate.opsForHash().put(key, field, strValue);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 添加键值到指定的哈希表中
     *
     * @param key    键
     * @param field  字段名
     * @param value  值
     * @param expire 过期时间(秒)
     */
    public static void hSet(String key, String field, Object value, long expire) {
        value = value == null ? "" : value;
        if (value instanceof String) {
            stringRedisTemplate.opsForHash().put(key, field, value.toString());
        } else {
            try {
                String strValue = objectMapper.writeValueAsString(value);
                stringRedisTemplate.opsForHash().put(key, field, strValue);

                // 设置过期时间
                expire(key, expire);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
    }


    /******************************获取*****************************/


    /**
     * 根据键获取值
     *
     * @param key 键
     */
    public static String get(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 根据键和对象类型获取值
     *
     * @param key   键
     * @param clazz 类对象
     */
    public static <T> T get(String key, Class<T> clazz) {
        String value = stringRedisTemplate.opsForValue().get(key);
        if (value == null) {
            return null;
        }
        try {
            return objectMapper.readValue(value, clazz);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 根据键和对象类型
     *
     * @param key           键
     * @param typeReference 类型
     */
    public static <T> T get(String key, TypeReference<T> typeReference) {
        String value = stringRedisTemplate.opsForValue().get(key);
        if (value == null) {
            return null;
        }
        try {
            return objectMapper.readValue(value, typeReference);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 从默认哈希表中获取指定名称的字段值
     *
     * @param field 字段名
     */
    public static String hGet(String field) {
        return hGet(DEFAULT_KEY, field);
    }


    /**
     * 从指定的哈希表中获取指定名称的字段值
     *
     * @param key   键
     * @param field 字段
     */
    public static String hGet(String key, String field) {
        return (String) stringRedisTemplate.opsForHash().get(key, field);
    }


    /**
     * 从默认哈希表中获取指定名称和对象类型的字段值
     *
     * @param field 字段名
     * @param clazz 类对象
     */
    public static <T> T hGet(String field, Class<T> clazz) {
        return hGet(DEFAULT_KEY, field, clazz);
    }


    /**
     * 从指定哈希表中获取指定名称和对象类型的字段值
     * 建议：clazz不要使用Map.class或者List.class，
     * 因为objectMapper无法知道集合中准确的bean类型，
     * 如果有此场景应用，请使用重载方法hGet参数为TypeReference类
     *
     * @param key   键
     * @param field 字段名
     * @param clazz 类对象
     */
    public static <T> T hGet(String key, String field, Class<T> clazz) {
        String value = (String) stringRedisTemplate.opsForHash().get(key, field);
        if (value == null) {
            return null;
        }
        try {
            return objectMapper.readValue(value, clazz);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 从默认哈希表中获取指定名称和对象类型的字段值
     *
     * @param key           键
     * @param typeReference 类型
     */
    public static <T> T hGet(String key, TypeReference<T> typeReference) {
        return hGet(DEFAULT_KEY, key, typeReference);
    }


    /**
     * 从指定哈希表中根据键和对象类型获取值 - 哈希存储
     *
     * @param key           键
     * @param field         字段名
     * @param typeReference 类型
     */
    public static <T> T hGet(String key, String field, TypeReference<T> typeReference) {
        String value = (String) stringRedisTemplate.opsForHash().get(key, field);
        if (value == null) {
            return null;
        }
        try {
            return objectMapper.readValue(value, typeReference);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    /******************************删除*****************************/

    /**
     * 删除默认哈希表中的键
     *
     * @param field 字段名
     */
    public static void hRemove(String field) {
        stringRedisTemplate.opsForHash().delete(DEFAULT_KEY, field);
    }


    /**
     * 删除指定哈希表中键的值
     *
     * @param key   键
     * @param field 字段名
     */
    public static void hRemove(String key, String field) {
        stringRedisTemplate.opsForHash().delete(key, field);
    }


    /******************************队列*****************************/
    public static Long listSize(String queueName) {
        return stringRedisTemplate.opsForList().size(queueName);
    }

    /**
     * 向指定队列左侧添加一个值 - 列表存储
     *
     * @param queueName 队列名
     * @param value     值
     */
    public static void leftPush(String queueName, String value) {
        stringRedisTemplate.opsForList().leftPush(queueName, value);
    }


    /**
     * 向指定队列右侧添加一个值 - 列表存储
     *
     * @param queueName 队列名
     * @param value     值
     */
    public static void rightPush(String queueName, String value) {
        stringRedisTemplate.opsForList().rightPush(queueName, value);
    }


    /**
     * 从指定队列左侧出列  - 列表存储
     *
     * @param queueName 队列名
     */
    public static String leftPop(String queueName) {
        return stringRedisTemplate.opsForList().leftPop(queueName);
    }

    /**
     * 从指定队列右侧出列  - 列表存储
     *
     * @param queueName 队列名
     */
    public static String rightPop(String queueName) {
        return stringRedisTemplate.opsForList().rightPop(queueName);
    }


    /**
     * 从指定队列中根据索引获取元素  - 列表存储
     *
     * @param queueName 队列名
     * @param index     索引
     */
    public static String index(String queueName, long index) {
        return stringRedisTemplate.opsForList().index(queueName, index);
    }


    /****************************** 其他 *****************************/

    /**
     * 指定缓存失效时间
     *
     * @param key    键
     * @param expire 时间(秒)
     */
    public static boolean expire(String key, long expire) {
        stringRedisTemplate.expire(key, expire, TimeUnit.SECONDS);
        return true;
    }


    /**
     * 发送消息
     *
     * @param channel 渠道名
     * @param message 消息
     */
    public static void convertAndSend(String channel, String message) {
        stringRedisTemplate.convertAndSend(channel, message);
    }
    /**
     * 根据关键字 模糊匹配出keys
     */
    public static Set<String>  getKeysByLike(String pattern){
        Set<String> keys = stringRedisTemplate.keys(pattern);
        return keys;
    }


    /****************************** 数据自增 *****************************/
    public static void incrementByNumber(String key, String hashKey, Double delta) {
        stringRedisTemplate.opsForHash().increment(key, hashKey, delta);
    }

    public static void incrementByNumber(String key, String hashKey, Integer delta) {
        stringRedisTemplate.opsForHash().increment(key, hashKey, delta);
    }

    /****************************** 消息发布订阅 *****************************/
    public static void publish(String channel, Object message) {
        stringRedisTemplate.convertAndSend(channel, message);
    }

    public static  Map<Object,Object> hgetAll(String key) {
        return stringRedisTemplate.opsForHash().entries(key);
    }

    public static  Long hremove(String key,Object...keys) {
        return stringRedisTemplate.opsForHash().delete(key, keys);
    }

    public static Set<Object> keys(String pattern) {
        return stringRedisTemplate.opsForHash().keys(pattern);
    }

}
